#nullable disable
namespace Crux.Core.Data;
/// <summary>
/// LongIdentifier
/// </summary>
public class LongIdentifier
{
    #region constants int
    /// <summary>
    /// 5位机器码
    /// </summary>
    private const int MachineIdBits = 5;
    /// <summary>
    /// 5位数据中心码
    /// </summary>
    private const int DataCenterIdBits = 5;
    /// <summary>
    /// 12为序列号
    /// </summary>
    private const int SequenceBits = 12;
    /// <summary>
    /// 机器码偏移量，12
    /// </summary>
    private const int MachineIdShift = SequenceBits;
    /// <summary>
    /// 数据中心偏移量，17
    /// </summary>
    private const int DataCenterIdShift = SequenceBits + MachineIdBits;
    /// <summary>
    /// 时间戳偏移量，22
    /// </summary>
    private const int TimestampLeftShift = SequenceBits + MachineIdBits + DataCenterIdBits;
    #endregion
    #region constants long
    /// <summary>
    /// 基准时间(一般为开发时的时间戳)
    /// </summary>
    private const long UniqueTime = 1609430400000L;
    /// <summary>
    /// 机器码上限
    /// </summary>
    private const long MachineIdUpperLimits = -1L ^ (-1L << MachineIdBits);
    /// <summary>
    /// 数据中心上限
    /// </summary>
    private const long DataCenterIdUpperLimits = -1L ^ (-1L << DataCenterIdBits);
    /// <summary>
    /// 序列号上限
    /// </summary>
    private const long SequenceMask = -1L ^ (-1L << SequenceBits);
    #endregion
    #region static single instance
    /// <summary>
    /// 单实例
    /// </summary>
    private static LongIdentifier Identifier;
    /// <summary>
    /// 是否初始化
    /// </summary>
    private static bool initialized = false;
    /// <summary>
    /// 对象锁
    /// </summary>
    private static object lockState = new object();
    #endregion
    #region status
    /// <summary>
    /// 生成Id时竞争锁
    /// </summary>
    private int _location = 0;
    /// <summary>
    /// 当前序列号
    /// </summary>
    private long _sequence = 0L;
    /// <summary>
    /// 上一次成功生成id时的时间戳
    /// </summary>
    private long _lastTimestamp = -1L;
    #endregion
    #region public fields
    /// <summary>
    /// 机器码
    /// </summary>
    public long MachineId { get; init; }
    /// <summary>
    /// 数据中心码
    /// </summary>
    public long DataCenterId { get; init; }
    /// <summary>
    /// 当前序列号
    /// </summary>
    public long Sequence => _sequence;
    #endregion
    #region .ctor
    /// <summary>
    /// .ctor
    /// </summary>
    /// <param name="machineId"></param>
    /// <param name="dataCenterId"></param>
    private LongIdentifier(
        long machineId,
        long dataCenterId)
    {
        if (machineId > MachineIdUpperLimits || machineId < 0)
            throw new ArgumentException(string.Format("machine Id can't be greater than {0} or less than 0 ", machineId));

        if (dataCenterId > DataCenterIdUpperLimits || dataCenterId < 0)
            throw new ArgumentException(string.Format("datacenter Id can't be greater than {0} or less than 0 ", dataCenterId));

        this.MachineId = machineId;
        this.DataCenterId = dataCenterId;
    }
    #endregion
    #region public static methods
    /// <summary>
    /// 初始化
    /// </summary>
    /// <param name="machineId"></param>
    /// <param name="dataCenterId"></param>
    public static void Initialize(long machineId, long dataCenterId) =>
        _ = LazyInitializer.EnsureInitialized(
            ref Identifier,
            ref initialized,
            ref lockState,
            () => Create(machineId, dataCenterId));
    /// <summary>
    /// 生产Id
    /// </summary>
    /// <returns></returns>
    public static long GenerateNewId()
    {
        if (!initialized)
        {
            Initialize(Environment.WorkingSet & 0x01f, Environment.MachineName.GetHashCode() & 0x01f);
        }

        return Identifier.InternalGenerate();
    }
    #endregion
    #region static internal methods
    /// <summary>
    /// 创建实例
    /// </summary>
    /// <param name="machineId"></param>
    /// <param name="dataCenterId"></param>
    /// <returns></returns>
    private static LongIdentifier Create(long machineId, long dataCenterId) => new LongIdentifier(machineId, dataCenterId);
    #endregion
    #region internal methods
    /// <summary>
    /// 生成Id
    /// </summary>
    /// <returns></returns>
    [MethodImpl(MethodImplOptions.NoInlining)]
    private long InternalGenerate()
    {
        while (true)
        {
            if (Interlocked.CompareExchange(ref _location, 1, 0) != 0)
            {
                Thread.Sleep(1);
                continue;
            }

            try
            {
                long executionTimestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
                if (executionTimestamp < _lastTimestamp)
                {
                    throw new Exception(string.Format("Clock moved backwards.  Refusing to generate id for {0} milliseconds",
                        _lastTimestamp - executionTimestamp));
                }
                else if (_lastTimestamp < executionTimestamp)
                {
                    _sequence = 0;
                }
                else
                {
                    _sequence = Interlocked.Increment(ref _sequence) & SequenceMask;
                    if (Sequence == 0)
                    {
                        executionTimestamp = NextMillisecond(_lastTimestamp);
                    }
                }

                _lastTimestamp = executionTimestamp;

                return (executionTimestamp - UniqueTime << TimestampLeftShift) |
                          DataCenterId << DataCenterIdShift |
                          MachineId << MachineIdShift |
                          Sequence;
            }
            finally
            {
                Interlocked.Exchange(ref _location, 0);
            }
        }
    }
    /// <summary>
    /// 获取下一微秒时间戳
    /// </summary>
    /// <param name="lastTimestamp"></param>
    /// <returns></returns>
    private static long NextMillisecond(long lastTimestamp)
    {
        long timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
        while (timestamp <= lastTimestamp)
        {
            timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
        }
        return timestamp;
    }
    #endregion


}